using System;
using System.Diagnostics;
using System.IO;

namespace ICommonLibrary.Security.GnuPG {

    /// <summary>
    /// This class is a wrapper class for GNU Privacy Guard (GnuPG). It execute the command 
    /// line program (gpg.exe) in an different process, redirects standard input (stdin),
    /// standard output (stdout) and standard error (stderr) streams, and monitors the 
    /// streams to fetch the results of the encryption/signing operation.<p/>
    /// 
    /// Please note that you must have INSTALLED GnuPG AND generated/imported the 
    /// appropriate keys before using this class.<p/>
    /// 
    /// GnuPG stands for GNU Privacy Guard and is GNU's tool for secure communication and 
    /// data storage. It can be used to encrypt data and to create digital signatures. It 
    /// includes an advanced key management facility and is compliant with the proposed 
    /// OpenPGP Internet standard as described in RFC 2440. As such, GnuPG is a complete 
    /// and free replacement for PGP.<p/>
    /// 
    /// This class has been developed and tested with GnuPG v1.2.0 (MingW32)<p/>
    /// 
    /// For more about GNU, please refer to http://www.gnu.org <br/>
    /// For more about GnuPG, please refer to http://www.gnupg.org <br/>
    /// For more about OpenPGP (RFC 2440), please refer to http://www.gnupg.org/rfc2440.html <br/>
    /// 
    /// 
    /// 1) gpg --list-keys --homedir="c:\GPG"  (this generates pubring and trustdb)
    /// 2) gpg --homedir="c:\GPG" --import Prodkey (this generate secring and import key)
    /// 3) gpg --homedir="C:\GPG" --always-trust  --encrypt --text  --armor  --yes --recipient "ENI Project 2 <eni@eni.prod.srv.westpac.com.au>" --output "d:\test.xml.asc" "c:\test.xml"
    /// </summary>
    public class GPG {

        /// <summary>
        /// GnuPG options.
        /// </summary>
        public GPGOptions Options { get; private set; }

        /// <summary>
        /// Timeout for GnuPG process, in milliseconds.
        /// 
        /// <p/>If the process doesn't exit before the end of the timeout period, the process is terminated (killed).
        /// 
        /// <p/>Defaults to 10000 (10 seconds).
        /// </summary>
        public int ProcessTimeOutMilliseconds { get; private set; }


        public GPG(GPGOptions options) {
            Options = options;
            ProcessTimeOutMilliseconds = 10000;// 10 seconds
        }

        public ProcessOutput EncryptFile(FileInfo inputFile, FileInfo outputFile) {

            string gpg = Options.BinDirectory + "\\gpg.exe";
            string gpgOptions = Options.BuildOptions() + " --output \"" + outputFile + "\" \"" + inputFile + "\" ";
            string cmd = gpg + " " + gpgOptions;

            if (!new FileInfo(gpg).Exists) {
                throw new Exception(gpg + " doesn't exist.");
            }

            using (Process process = Process.Start(CreateProcessStartInfo(gpg, gpgOptions))) {

                // Send pass phrase, if any
                if (!string.IsNullOrEmpty(Options.Passphrase)) {
                    process.StandardInput.WriteLine(Options.Passphrase);
                    process.StandardInput.Flush();
                }

                string outputString = process.StandardOutput.ReadToEnd();
                string errorString = process.StandardError.ReadToEnd();

                if (process.WaitForExit(ProcessTimeOutMilliseconds)) {
                    return new ProcessOutput {
                        Command = cmd,
                        Exitcode = process.ExitCode,
                        OutputString = outputString,
                        ErrorString = errorString
                    };
                }

                // Process timeout: PGP hung somewhere...
                process.Kill();

                return new ProcessOutput {
                    Command = cmd,
                    Exitcode = 1,
                    OutputString = "",
                    ErrorString = "Timed out after " + ProcessTimeOutMilliseconds + " milliseconds"
                };
            }
        }

        public ProcessOutput EncryptText(string inputText) {

            string gpg = Options.BinDirectory + "\\gpg.exe";
            string gpgOptions = Options.BuildOptions();
            string cmd = gpg + " " + gpgOptions;

            if (!new FileInfo(gpg).Exists) {
                throw new Exception(gpg + " doesn't exist.");
            }

            using (Process process = Process.Start(CreateProcessStartInfo(gpg, gpgOptions))) {

                // Send pass phrase, if any
                if (!string.IsNullOrEmpty(Options.Passphrase)) {
                    process.StandardInput.WriteLine(Options.Passphrase);
                    process.StandardInput.Flush();
                }

                // Send input text
                process.StandardInput.Write(inputText);
                process.StandardInput.Flush();
                process.StandardInput.Close();

                string outputString = process.StandardOutput.ReadToEnd();
                string errorString = process.StandardError.ReadToEnd();

                if (process.WaitForExit(ProcessTimeOutMilliseconds)) {
                    return new ProcessOutput {
                        Command = cmd,
                        Exitcode = process.ExitCode,
                        OutputString = outputString,
                        ErrorString = errorString
                    };
                }

                // Process timeout: PGP hung somewhere...
                process.Kill();

                return new ProcessOutput {
                    Command = cmd,
                    Exitcode = 1,
                    OutputString = "",
                    ErrorString = "Timed out after " + ProcessTimeOutMilliseconds + " milliseconds"
                };
            }
        }

        private ProcessStartInfo CreateProcessStartInfo(string gpg, string gpgOptions) {
            return new ProcessStartInfo(gpg, gpgOptions) {
                WorkingDirectory = Options.BinDirectory,
                CreateNoWindow = true,
                UseShellExecute = false,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,
                RedirectStandardError = true
            };
        }

    }

}